# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from hysop.tools.decorators import debug
from hysop.tools.htypes import check_instance, first_not_None, to_tuple
from hysop.tools.transposition_states import TranspositionState
from hysop.constants import Backend, MemoryOrdering
from hysop.core.graph.computational_operator import ComputationalGraphOperator
from hysop.fields.continuous_field import Field
from hysop.core.graph.graph import op_apply
[docs]
class Noop(ComputationalGraphOperator):
"""An operator that does nothing and implements apply as noop."""
@op_apply
def apply(self, **kwds):
"""This is a noop."""
pass
[docs]
@classmethod
def supported_backends(cls):
return Backend.all
[docs]
@classmethod
def supports_multiple_field_topologies(cls):
return True
[docs]
@classmethod
def supports_multiple_topologies(cls):
return True
[docs]
@classmethod
def supports_mpi(cls):
return True
[docs]
def get_node_requirements(self):
from hysop.core.graph.node_requirements import OperatorRequirements
reqs = OperatorRequirements(
self,
enforce_unique_transposition_state=False,
enforce_unique_topology_shape=False,
enforce_unique_memory_order=False,
)
return reqs
[docs]
class ForceTopologyState(Noop):
"""
Dummy operator used mostly for testing purposes where we need a consistant output
topology and data layout (local transposition state, array backend, memory ordering).
This operator will just impose a given transposition states, a given memory ordering
and a given backend, to all of its input fields. This forces the graph generator to
generate additional operators and topologies to comply with those field requirements.
If transposition_state is not given, all input fields will be imposed to be in natural
transposition order (YX in 2D and ZYX in 3D).
If memory_order is not given, all input fields will be imposed to be C_CONTIGUOUS.
If backend is not given, all input fields will be imposed to live on Backend.HOST.
"""
@debug
def __new__(
cls,
fields,
variables,
tstate=None,
memorder=None,
backend=None,
extra_kwds=None,
mpi_params=None,
cl_env=None,
**kwds,
):
kwds.setdefault("mpi_params", None)
return super().__new__(cls, input_fields=None, output_fields=None, **kwds)
@debug
def __init__(
self,
fields,
variables,
tstate=None,
memorder=None,
backend=None,
extra_kwds=None,
mpi_params=None,
cl_env=None,
**kwds,
):
extra_kwds = first_not_None(extra_kwds, {})
fields = to_tuple(fields)
check_instance(fields, tuple, values=Field, minsize=1)
f0 = fields[0]
for f in fields[1:]:
if f.domain.dim != f0.domain.dim:
raise ValueError("Domain dimension mismatch.")
dim = f0.domain.dim
tstate = first_not_None(tstate, TranspositionState[dim].default())
memorder = first_not_None(memorder, MemoryOrdering.ANY)
if tstate.dimension() != dim:
msg = "Transposition state {} has dimension {} which is "
msg += "incompatible with domain dimension {}."
msg = msg.format(tstate, tstate.dimension(), dim)
raise ValueError(msg)
input_fields = {k: variables[k] for k in fields}
output_fields = {k: variables[k] for k in fields}
cl_env = first_not_None(cl_env, extra_kwds.get("cl_env", None))
mpi_params = first_not_None(
mpi_params,
extra_kwds.get("mpi_params", None),
getattr(cl_env, "mpi_params", None),
)
extra_kwds.setdefault("mpi_params", mpi_params)
extra_kwds.setdefault("cl_env", cl_env)
kwds.setdefault("mpi_params", mpi_params)
super().__init__(input_fields=input_fields, output_fields=output_fields, **kwds)
self.tstate = tstate
self.memorder = memorder
self.backend = first_not_None(backend, Backend.HOST)
self.extra_kwds = first_not_None(extra_kwds, {})
[docs]
@debug
def get_field_requirements(self):
from hysop.topology.topology_descriptor import TopologyDescriptor
from hysop.fields.field_requirements import DiscreteFieldRequirements
for field, topo_descriptor in self.input_fields.items():
topo_descriptor = TopologyDescriptor.build_descriptor(
backend=self.backend,
operator=self,
field=field,
handle=topo_descriptor,
**self.extra_kwds,
)
self.input_fields[field] = topo_descriptor
for field, topo_descriptor in self.output_fields.items():
topo_descriptor = TopologyDescriptor.build_descriptor(
backend=self.backend,
operator=self,
field=field,
handle=topo_descriptor,
**self.extra_kwds,
)
self.output_fields[field] = topo_descriptor
# and we use default DiscreteFieldRequirements (ie. no min ghosts, no max ghosts,
# can_split set to True in all directions, all TranspositionStates).
input_field_requirements = {}
for field, topo_descriptor in self.input_fields.items():
if topo_descriptor is None:
req = None
else:
workdim = topo_descriptor.domain.dim
req = DiscreteFieldRequirements(self, self.input_fields, field)
req.axes = (self.tstate.axes,)
req.memory_order = self.memorder
input_field_requirements[field] = req
output_field_requirements = {}
for field, topo_descriptor in self.output_fields.items():
if topo_descriptor is None:
req = None
else:
workdim = topo_descriptor.domain.dim
req = DiscreteFieldRequirements(self, self.output_fields, field)
req.axes = (self.tstate.axes,)
req.memory_order = self.memorder
output_field_requirements[field] = req
from hysop.fields.field_requirements import OperatorFieldRequirements
requirements = OperatorFieldRequirements()
requirements.update_inputs(input_field_requirements)
requirements.update_outputs(output_field_requirements)
return requirements